package com.cskaoyan.mall.search.service.impl;

import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.AggregateBuilders;
import com.cskaoyan.mall.common.constant.RedisConst;
import com.cskaoyan.mall.product.dto.CategoryHierarchyDTO;
import com.cskaoyan.mall.product.dto.PlatformAttributeInfoDTO;
import com.cskaoyan.mall.product.dto.SkuInfoDTO;
import com.cskaoyan.mall.product.dto.TrademarkDTO;
import com.cskaoyan.mall.search.client.ProductApiClient;
import com.cskaoyan.mall.search.converter.GoodsConverter;
import com.cskaoyan.mall.search.dto.GoodsDTO;
import com.cskaoyan.mall.search.dto.SearchResponseAttrDTO;
import com.cskaoyan.mall.search.dto.SearchResponseDTO;
import com.cskaoyan.mall.search.dto.SearchResponseTmDTO;
import com.cskaoyan.mall.search.model.Goods;
import com.cskaoyan.mall.search.model.SearchAttr;
import com.cskaoyan.mall.search.param.SearchParam;
import com.cskaoyan.mall.search.repository.GoodsRepository;
import com.cskaoyan.mall.search.service.SearchService;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.elasticsearch.search.aggregations.bucket.nested.Nested;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.redisson.api.RScoredSortedSet;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.HighlightQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Service
public class SearchServiceImpl implements SearchService {
    @Autowired
    ProductApiClient productApiClient;
    @Autowired
    GoodsRepository goodsRepository;
    @Autowired
    RedissonClient redissonClient;

    @Autowired
    ElasticsearchRestTemplate restTemplate;

    @Autowired
    GoodsConverter goodsConverter;

    ExecutorService threadPool=Executors.newFixedThreadPool(12);

    @Override
    public void upperGoods(Long skuId) {
        //上架商品，要先获取商品的相关信息，然后要分配线程，多线程处理请求数据
        //需要的结构是什么？
        Goods goods=new Goods();
        //然后在其中要调用product的接口
        //sku信息
        CompletableFuture<SkuInfoDTO> skuInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            SkuInfoDTO skuInfo = productApiClient.getSkuInfo(skuId);
            goods.setId(skuId);
            goods.setDefaultImg(skuInfo.getSkuDefaultImg());
            goods.setTitle(skuInfo.getSkuName());
            goods.setPrice(skuInfo.getPrice().doubleValue());
            goods.setTmId(skuInfo.getTmId());
            return skuInfo;
        }, threadPool);
        //平台属性信息
        CompletableFuture<Void> attrCompletableFuture = CompletableFuture.runAsync(() -> {
            List<PlatformAttributeInfoDTO> attrList = productApiClient.getAttrList(skuId);
            //因为是sku信息，所以对应的属性值只会有一个
            List<SearchAttr> searchAttrs = attrList.stream().map(attr -> {
                SearchAttr searchAttr = new SearchAttr();
                searchAttr.setAttrId(attr.getId());
                searchAttr.setAttrName(attr.getAttrName());
                //这个地方我们先默认如果没有value就会被过滤，不会被渲染
                searchAttr.setAttrValue(attr.getAttrValueList().get(0).getValueName());
                return searchAttr;
            }).toList();
            goods.setAttrs(searchAttrs);

        }, threadPool);
        //一/二/三级分类信息
        //这个是一元依赖
        CompletableFuture<Void> categoryCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync((skuInfo) -> {
            CategoryHierarchyDTO categoryView = productApiClient.getCategoryView(skuInfo.getThirdLevelCategoryId());
            //然后存入值就好啦
            goods.setFirstLevelCategoryId(categoryView.getFirstLevelCategoryId());
            goods.setFirstLevelCategoryName(categoryView.getFirstLevelCategoryName());

            goods.setSecondLevelCategoryId(categoryView.getSecondLevelCategoryId());
            goods.setSecondLevelCategoryName(categoryView.getSecondLevelCategoryName());

            goods.setThirdLevelCategoryId(categoryView.getThirdLevelCategoryId());
            goods.setThirdLevelCategoryName(categoryView.getThirdLevelCategoryName());
        }, threadPool);
        //品牌信息
        CompletableFuture<Void> trademarkCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync((skuInfo) -> {
            TrademarkDTO trademark = productApiClient.getTrademark(skuInfo.getTmId());
            goods.setTmName(trademark.getTmName());
            goods.setTmLogoUrl(trademark.getLogoUrl());
        }, threadPool);

        //最后多元依赖存入结果
        CompletableFuture<Void> finalCompletableFuture = CompletableFuture.allOf(trademarkCompletableFuture, categoryCompletableFuture, skuInfoCompletableFuture, attrCompletableFuture);
        finalCompletableFuture.join();

        goodsRepository.save(goods);
    }

    @Override
    public void lowerGoods(Long skuId) {
        goodsRepository.deleteById(skuId);
    }

    @Override
    public void incrHotScore(Long skuId) {
        //要先存redis里面再存es内
        RScoredSortedSet<Long> sortedSet = redissonClient.getScoredSortedSet(RedisConst.HOT_SCORE);
        sortedSet.addScore(skuId,1);

        Double score = sortedSet.getScore(skuId);
        if (score>=2){
            //变更对应的es内容
            Optional<Goods> goodsOptional = goodsRepository.findById(skuId);
            Goods goods = goodsOptional.get();
            goods.setHotScore((long) (goods.getHotScore()+score));
            goodsRepository.save(goods);
            sortedSet.add(0,skuId);
        }

    }

    @Override
    public SearchResponseDTO search(SearchParam searchParam) throws IOException {
        //这个地方要构造请求条件
        //再写一遍吧，有点忘了。呜呜呜呜呜，还是忘
        NativeSearchQueryBuilder nativeSearchQueryBuilder=new NativeSearchQueryBuilder();
        
        buildRequest(nativeSearchQueryBuilder,searchParam);
        NativeSearchQuery nativeSearchQuery = nativeSearchQueryBuilder.build();

        SearchHits<Goods> search = restTemplate.search(nativeSearchQuery, Goods.class);
        //然后解析返回值内容即可
        SearchResponseDTO searchResponseDTO=parseQuery(search,searchParam);

        return searchResponseDTO;
    }

    @Override
    public String goodsListStr() {
        //查询全部数据，获取对应的字符串
        Iterable<Goods> iterable = goodsRepository.findAll();
        StringBuffer result=new StringBuffer();
        //查询所有
        Iterator<Goods> goodsIterator = iterable.iterator();
        while (goodsIterator.hasNext()){
            Goods goods = goodsIterator.next();
            result.append("商品名称："+goods.getTitle()
                    +"\n商品价格："+goods.getPrice()
                    +"\n商品品牌："+goods.getTmName()
                    +"\n商品类名："+goods.getFirstLevelCategoryName()+"/"+goods.getSecondLevelCategoryName()+"/"+goods.getThirdLevelCategoryName()
                    +"\n商品相关属性：{\n");
            for (SearchAttr attr : goods.getAttrs()) {
                result.append(attr.getAttrName()+":"+attr.getAttrValue()+"\n");
            }
            result.append("}\n");
            result.append("\n");
        }
        return result.toString();
    }

    private SearchResponseDTO parseQuery(SearchHits<Goods> search, SearchParam searchParam) {
        SearchResponseDTO searchResponseDTO=new SearchResponseDTO();
        //先把goodsList列表获取
        List<SearchHit<Goods>> searchHits = search.getSearchHits();
        List<GoodsDTO> goodsDTOS = searchHits.stream().map(searchHit -> {
            Goods content = searchHit.getContent();
            if (!searchHit.getHighlightFields().isEmpty()){
                List<String> title = searchHit.getHighlightField("title");
                content.setTitle(title.get(0));
            }
            return goodsConverter.goodsPO2DTO(content);
        }).toList();
        searchResponseDTO.setGoodsList(goodsDTOS);
        Aggregations aggregations = (Aggregations) search.getAggregations().aggregations();

        //然后获取品牌内容
        Terms tmIdAgg = aggregations.get("tmId_agg");
        List<? extends Terms.Bucket> buckets = tmIdAgg.getBuckets();
        List<SearchResponseTmDTO> tmDTOS = buckets.stream().map(bucket -> {
            SearchResponseTmDTO searchResponseTmDTO = new SearchResponseTmDTO();
            searchResponseTmDTO.setTmId((Long) bucket.getKey());
            Aggregations bucketAggregations = bucket.getAggregations();
            Terms tmUrlAgg = bucketAggregations.get("tmUrl_agg");
            searchResponseTmDTO.setTmLogoUrl(tmUrlAgg.getBuckets().get(0).getKeyAsString());
            Terms tmNameAgg = bucketAggregations.get("tmName_agg");
            searchResponseTmDTO.setTmName(tmNameAgg.getBuckets().get(0).getKeyAsString());
            return searchResponseTmDTO;
        }).toList();
        searchResponseDTO.setTrademarkList(tmDTOS);

        //然后写属性
        Nested attrAgg = aggregations.get("attrAgg");
        Aggregations attrAggAggregations = attrAgg.getAggregations();
        Terms attrIdAgg = attrAggAggregations.get("attrIdAgg");
        List<SearchResponseAttrDTO> searchResponseAttrDTOS = attrIdAgg.getBuckets().stream().map(bucket -> {
            SearchResponseAttrDTO searchResponseAttrDTO = new SearchResponseAttrDTO();
            searchResponseAttrDTO.setAttrId((Long) bucket.getKey());
            Aggregations bucketAggregations = bucket.getAggregations();
            Terms attrNameAgg = bucketAggregations.get("attrNameAgg");
            searchResponseAttrDTO.setAttrName(attrNameAgg.getBuckets().get(0).getKeyAsString());
            Terms attrValueAgg = bucketAggregations.get("attrValueAgg");
            List<String> attrValueList = attrValueAgg.getBuckets().stream()
                    .map(MultiBucketsAggregation.Bucket::getKeyAsString)
                    .toList();
            searchResponseAttrDTO.setAttrValueList(attrValueList);

            return searchResponseAttrDTO;
        }).toList();
        searchResponseDTO.setAttrsList(searchResponseAttrDTOS);

        //写完属性写总页面数
        long totalHits = search.getTotalHits();
        Integer pageSize = searchParam.getPageSize();
        searchResponseDTO.setTotal(totalHits);
        searchResponseDTO.setPageSize(pageSize);
        searchResponseDTO.setPageNo(searchParam.getPageNo());
        Long totalPage= (long) Math.ceil((double) totalHits/(double) pageSize);
        searchResponseDTO.setTotalPages(totalPage);
        return searchResponseDTO;
    }


    private void buildRequest(NativeSearchQueryBuilder nativeSearchQueryBuilder,SearchParam searchParam) {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        if (StringUtils.isNotBlank(searchParam.getKeyword())){
            MatchQueryBuilder titleMatchBuilder = QueryBuilders.matchQuery("title", searchParam.getKeyword());
            boolQueryBuilder.must(titleMatchBuilder);
        }
        if (searchParam.getThirdLevelCategoryId()!=null){
            TermQueryBuilder thirdLevelCategoryIdTerm = QueryBuilders.termQuery("thirdLevelCategoryId", searchParam.getThirdLevelCategoryId());
            boolQueryBuilder.filter(thirdLevelCategoryIdTerm);
        }
        if (StringUtils.isNotBlank(searchParam.getTrademark())){
            String[] trademarkSplit = searchParam.getTrademark().split(":");
            TermQueryBuilder trademarkTerm = QueryBuilders.termQuery("tmId", trademarkSplit[0]);
            boolQueryBuilder.filter(trademarkTerm);
        }
        //然后要循环写属性的相关内容
        String[] props = searchParam.getProps();
        if (props != null){
            for (String prop : props) {
                String[] propSplit = prop.split(":");
                BoolQueryBuilder attrBool = QueryBuilders.boolQuery();
                TermQueryBuilder attrIdTerm = QueryBuilders.termQuery("attrs.attrId", propSplit[0]);
                TermQueryBuilder attrValueTerm = QueryBuilders.termQuery("attrs.attrValue", propSplit[1]);
                attrBool.filter(attrIdTerm);
                attrBool.filter(attrValueTerm);
                NestedQueryBuilder attrsNestedQuery = QueryBuilders.nestedQuery("attrs", attrBool, ScoreMode.None);
                boolQueryBuilder.filter(attrsNestedQuery);
            }
        }
        nativeSearchQueryBuilder.withQuery(boolQueryBuilder);

        //然后写分页
        PageRequest pageRequest = PageRequest.of(searchParam.getPageNo() - 1, searchParam.getPageSize());
        nativeSearchQueryBuilder.withPageable(pageRequest);

        //然后写排序
        if (StringUtils.isNotBlank(searchParam.getOrder())){
            String[] orderSplit = searchParam.getOrder().split(":");
            String orderName= orderSplit[0].equals("1") ?"hotScore":"price";
            Sort.Direction orderDirection=orderSplit[1].equals("desc")?Sort.Direction.DESC:Sort.Direction.ASC;
            Sort order = Sort.by(orderDirection, orderName);
            nativeSearchQueryBuilder.withSort(order);
        }

        //写完排序写高亮
        if (StringUtils.isNotBlank(searchParam.getKeyword())){
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("title").preTags("<span style='color:red'>").postTags("</span>");
            nativeSearchQueryBuilder.withHighlightBuilder(highlightBuilder);
        }

        //最后写聚合，聚合一定要写
        TermsAggregationBuilder tmIdAgg = AggregationBuilders.terms("tmId_agg").field("tmId")
                .subAggregation(AggregationBuilders.terms("tmName_agg").field("tmName"))
                .subAggregation(AggregationBuilders.terms("tmUrl_agg").field("tmLogoUrl"));
        nativeSearchQueryBuilder.withAggregations(tmIdAgg);

        NestedAggregationBuilder attrNested = AggregationBuilders.nested("attrAgg", "attrs")
                .subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrs.attrId")
                        .subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"))
                        .subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue"))
                );
        nativeSearchQueryBuilder.withAggregations(attrNested);


    }
}
